// * 丢失的 this

// 例子：使用 setTimeout 时 this 丢失。

let user = {
	name: 'Jake',
	sayHi() {
		console.log(`Hi, I'm ${this.name}.`)
	},
}

// * 浏览其中的 setTimeout 会默认为回调函数（仅限函数声明和函数表达式）设定 this = window。
// * 在其他类似的情况下（严格模式下），通常 this 会变为 undefined。
// * node 环境下 setTimeout 会为回调函数设定 this = 一个计时器对象。
setTimeout(user.sayHi, 1000) // Hi, I'm undefined

// 上面相当于是这样在调用，所以 f 函数的 this 就丢失了。
// let f = user.sayHi;
// setTimeout(() => f(), 1000) // Hi, I'm undefined

// **********************************************************************************************************************************

// * 解决方案1：包装器
setTimeout(() => user.sayHi(), 1000) // Hi, I'm Jake

// * 解决方案1的漏洞：在 1s 后打印前，user 被改变了。
// setTimeout(() => user.sayHi(), 1000) // Hello
// user.sayHi = function () {
// 	console.log('Hello')
// }

// **********************************************************************************************************************************

// * 解决方案2：bind
// 语法：func.bind(context) 的结果是一个特殊的类似于函数的“外来对象”（相当于一个被指定了this的新的函数），它可以向函数一样被调用。

// 例子1：
let user2 = {
	name: 'Jake',
}

function func() {
	console.log(this.name)
}

// 将 func 中的 this 绑定到 user2
const fn = func.bind(user2)

fn() // Jake

// ************************************

// 例子2：
let user3 = {
	name: 'Ben',
	sayHello() {
		console.log(this.name)
	},
}

// 给 user3.sayHello 方法绑定了 this = user3，然后返回一个新的函数存储到 sayHello 中。
const sayHello = user3.sayHello.bind(user3)

sayHello() // Ben

setTimeout(sayHello, 1000) // Ben

// user3.sayHello 被改变了 但 sayHello 不受影响。
user3.sayHello = () => console.log(123)

// ************************************

// 例子3：
let user4 = {
	name: 'John',
	say(phrase) {
		console.log(`${phrase}, ${this.name}`)
	},
}

let say = user4.say.bind(user4)

// 虽然是一个新的函数对象，但它接收参数和函数内的代码形式没有改变。
say('Hello') // Hello, John
say('Hi') // Hi, John

// ************************************

// 例子4：给对象中所有函数绑定this

let user5 = {
	fn1() {
		console.log(this)
	},
	fn2() {
		console.log(this)
	},
	fn3() {
		console.log(this)
	},
}

function bindAll(obj) {
	for (const key in obj) {
		if (typeof user5[key] === 'function') user5[key].bind(user5)
	}
}
// ************************************

// 例子5：bind 可以绑定 this 也可以绑定默认参数（其实没必要这么做，通过形参指定默认参数即可）。

function mul(a, b) {
	return a * b
}

let double = mul.bind(null, 2)

console.log(double(3)) // 6
console.log(double(4)) // 8

// **********************************************************************************************************************************

// * 任务一：一个函数不能被重绑定（re-bound）。
function fns() {
	console.log(this.name)
}

fns = fns.bind({ name: 'Jake' }).bind({ name: 'John' })

fns() // Jake

// * 任务二：bind 后的新函数，除了参数 和 函数体 的内容会被保存外，其他原函数的自定义属性啥的不会被继承。
function ff() {
	console.log(this.name)
}

ff.test = 5

let bound = ff.bind({ name: 'Jake' })

bound() // Jake

console.log(bound.test) // undefined
